//
//  MyCBCentrolManagerDelegate.swift
//  Teacher
//
//  Created by 王麒皓 on 2023/7/16.
//

import Foundation
import CoreBluetooth
import SwiftUI
import os

class MyCBCentralManagerDelegate: NSObject, CBCentralManagerDelegate {
    var centralManager: CBCentralManager?
    @Environment(\.scenePhase) var scene
    @Binding var BLEProblem: String?
    var discoveredPeripheral: CBPeripheral?
    var id: CBUUID
    var completion: (() -> Void)?
    var timeOutCompletion: (() -> Void)?
    var stopScan: ((_ stopScan: @escaping (() -> Void)) -> Void)
    var initialTime = Date().timeIntervalSince1970
    
    @available(*, renamed: "init()")
    init(id: CBUUID, BLEProblem: Binding<String?>, stopScan: @escaping ((_ stopScan: @escaping (() -> Void)) -> Void),
         timeOutCompletion: (() -> Void)?, completion: (() -> Void)?) {
        self.id = id
        self._BLEProblem = BLEProblem
        self.completion = completion
        self.timeOutCompletion = timeOutCompletion
        self.stopScan = stopScan
        super.init()
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
        self.stopScan({
            self.centralManager!.stopScan()
            os_log("Scan stopped!")
        })
        DispatchQueue.main.asyncAfter(deadline: .now() + 15) {
            self.timeOutCompletion?()
            self.centralManager?.stopScan()
        }
    }
    
    private func retrievePeripheral() {
        
        let connectedPeripherals: [CBPeripheral] = (centralManager?.retrieveConnectedPeripherals(withServices: [id]))!
        
        os_log("Found connected Peripherals with transfer service: %@", connectedPeripherals)
        
        if let connectedPeripheral = connectedPeripherals.last {
            os_log("Connecting to peripheral %@", connectedPeripheral)
            self.discoveredPeripheral = connectedPeripheral
            centralManager?.connect(connectedPeripheral, options: nil)
        } else {
            // We were not connected to our counterpart, so start scanning
            centralManager?.scanForPeripherals(withServices: [id],
                                               options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
        }
    }
    
    private func cleanup() {
        // Don't do anything if we're not connected
        guard let discoveredPeripheral = discoveredPeripheral,
            case .connected = discoveredPeripheral.state else { return }
        
        for service in (discoveredPeripheral.services ?? [] as [CBService]) {
            for characteristic in (service.characteristics ?? [] as [CBCharacteristic]) {
                if characteristic.uuid == id && characteristic.isNotifying {
                    // It is notifying, so unsubscribe
                    self.discoveredPeripheral?.setNotifyValue(false, for: characteristic)
                }
            }
        }
        
        // If we've gotten this far, we're connected, but we're not subscribed, so we just disconnect
        centralManager?.cancelPeripheralConnection(discoveredPeripheral)
    }
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        switch central.state {
        case .poweredOn:
            // ... so start working with the peripheral
            os_log("CBManager is powered on")
            DispatchQueue.main.async {
                self.BLEProblem = nil
            }
            retrievePeripheral()
        case .poweredOff:
            os_log("CBManager is not powered on")
            // In a real app, you'd deal with all the states accordingly
            DispatchQueue.main.async {
                self.BLEProblem = "[蓝牙未打开]"
            }
            return
        case .resetting:
            os_log("CBManager is resetting")
            // In a real app, you'd deal with all the states accordingly
            DispatchQueue.main.async {
                self.BLEProblem = "[蓝牙状态错误]"
            }
            return
        case .unauthorized:
            // In a real app, you'd deal with all the states accordingly
            os_log("You are not authorized to use Bluetooth")
            DispatchQueue.main.async {
                self.BLEProblem = "[未授权蓝牙，请到设置中授权]"
            }
            return
        case .unknown:
            os_log("CBManager state is unknown")
            // In a real app, you'd deal with all the states accordingly
            DispatchQueue.main.async {
                self.BLEProblem = "[未知错误]"
            }
            return
        case .unsupported:
            os_log("Bluetooth is not supported on this device")
            // In a real app, you'd deal with all the states accordingly
            DispatchQueue.main.async {
                self.BLEProblem = "[此设备不支持蓝牙]"
            }
            return
        @unknown default:
            os_log("A previously unknown central manager state occurred")
            // In a real app, you'd deal with yet unknown cases that might occur in the future
            DispatchQueue.main.async {
                self.BLEProblem = "[未知错误]"
            }
            return
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any], rssi RSSI: NSNumber) {
        
        // Reject if the signal strength is too low to attempt data transfer.
        // Change the minimum RSSI value depending on your app’s use case.
        guard RSSI.intValue >= -50
            else {
                os_log("Discovered perhiperal not in expected range, at %d", RSSI.intValue)
                return
        }
        
        os_log("Discovered %s at %d", String(describing: peripheral.name), RSSI.intValue)
        
        // Device is in range - have we already seen it?
        if discoveredPeripheral != peripheral {
            
            // Save a local copy of the peripheral, so CoreBluetooth doesn't get rid of it.
            discoveredPeripheral = peripheral
            
            // And finally, connect to the peripheral.
            os_log("Connecting to perhiperal %@", peripheral)
            centralManager?.connect(peripheral, options: nil)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        os_log("Peripheral Connected")
        
        // Stop scanning
        centralManager?.stopScan()
        os_log("Scanning stopped")
        
        // MARK: Run target codes
        if let completion = completion {
            completion()
        }
    }
}

class BLESignTool {
    var classBLEName: String
    var classData: CoreData
    var eventIndex: Int
    var delegate: MyCBCentralManagerDelegate? = nil
    
    init(classBLEName: String, classData: CoreData, eventIndex: Int) {
        self.classBLEName = classBLEName
        self.classData = classData
        self.eventIndex = eventIndex
    }
    
    func check() async -> Bool {
        return await withCheckedContinuation { continuation in
            self.delegate = MyCBCentralManagerDelegate(id: CBUUID(string: self.classBLEName), BLEProblem: .constant(""), stopScan: {_ in}, timeOutCompletion: {
                continuation.resume(returning: false)
            }, completion: {
                continuation.resume(returning: true)
            })
        }
    }
}
